package lib.rpc;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;

import lib.ArgumentValidator;
import lib.logging.ILogger;
import lib.messaging.Communicator;
import lib.messaging.IReceiver;
import lib.messaging.ISender;

/**
 * Provides the RPC functionality for client
 *
 * @implements {@link IClientRPCFunctionality}
 * @author abhip
 *
 */
public class ClientRPCFunctionality implements IClientRPCFunctionality
{

    private ILogger logger;
    private double packetDropProbability;
    private int timeout;


    public ClientRPCFunctionality(
            ILogger logger,
            int timeout,
            double packetDropProbability)
    {
        ArgumentValidator.IsNotNull("logger", logger);
        ArgumentValidator.IsGreaterThanOrEqualTo("timeout", timeout, 0);
        ArgumentValidator.IsBetweenBothInclusive(
                "packetDropProbability",
                packetDropProbability,
                0.0,
                1.0);

        this.logger = logger;
        this.timeout = timeout;
        this.packetDropProbability = packetDropProbability;
    }

    /**
     * Serializes the rpcInvokePacket and sends it to the server, and waits for
     * a response
     *
     * @param rpcInvokePacket
     * @return deserialized rpcResultPacked obtained from the server
     * @throws IOException
     * @throws ClassNotFoundException
     */
    @Override
    public RPCResultPacket Invoke(RPCInvokePacket rpcInvokePacket)
            throws IOException, ClassNotFoundException
    {
        SendInvokePacket(rpcInvokePacket);
        return ReceiveResultPacket(rpcInvokePacket);
    }

    private RPCResultPacket ReceiveResultPacket(RPCInvokePacket rpcInvokePacket)
            throws ClassNotFoundException, IOException
    {
        IReceiver receiver = new Communicator(
                packetDropProbability,
                logger);

        byte[] responseData = receiver.ReceiveBytes(rpcInvokePacket.ClientAddress().Port(), timeout);
        RPCResultPacket rpcResultPacket = RPCResultPacket.Deserialize(responseData);

        if (rpcResultPacket.RequestID().equals(rpcInvokePacket.RequestID()))
        {
            return rpcResultPacket;
        }
        else
        {
            logger.LogError(
                    "SequenceID of rpcResultPacket does not match rpcInvokePacket! Probably got some other packet. Ignoring it."
                            + "rpcInvokePacket: %s, rpcResultPacket:%s ",
                    rpcInvokePacket.toString(),
                    rpcResultPacket.toString());

            return ReceiveResultPacket(rpcInvokePacket);
        }
    }

    private void SendInvokePacket(RPCInvokePacket rpcInvokePacket)
            throws UnsupportedEncodingException, UnknownHostException, IOException
    {
        ISender sender = new Communicator(
                packetDropProbability,
                logger);

        sender.SendBytes(rpcInvokePacket.ServerAddress(), rpcInvokePacket.Serialize());
    }
}
